﻿'''
Copyright (c) 2010 beinghuman.ng (on Google Code)

This file is part of notewhal.
'''
gpl = '''
notewhal is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

notewhal is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with notewhal. If not, see <http://www.gnu.org/licenses/>.
'''

import wx, ConfigParser, webbrowser, os, subprocess, copy, threading, sys, imaplib, json
import urllib2 as net
from PIL import Image,ImageOps,ImageChops
from wx import propgrid as wxpg
from string import Template
from wx.lib import newevent
from version import version

if not __debug__:
	import tempfile
	sys.stdout = open(os.path.join(tempfile.gettempdir(),'notewhal_stdout.log'), 'a+')
	sys.stderr = open(os.path.join(tempfile.gettempdir(),'notewhal_stderr.log'), 'a+')

frame = 0
NewMessagesEvent, EVT_NEW_MESSAGES = newevent.NewEvent()

class wthread(threading.Thread):
	def __init__(self,function,c,data):
		threading.Thread.__init__(self)
		self.function = function
		self.c = c
		self.data = data
		self.start()
	def run(self):
		self.function(self.c,self.data)

def CheckMessages(c,data):

	status = []
	checked_profiles = {}

	for item in c:

		try:
			NAME = data[item].setdefault('name','')
			PROTOCOL = int(data[item].setdefault('protocol',''))
		except: continue #TODO: put error in status
		
		if NAME =='' or PROTOCOL == '': continue #TODO: put error in status

		i = 0
		checked_profiles[item] = i
		
		if PROTOCOL == 0 or PROTOCOL == 1:
		
			try:
				SERVER = data[item].setdefault('server','')
				USERNAME = data[item].setdefault('username','')
				PASSWORD = data[item].setdefault('password','')
			except: continue #TODO: put error in status
			
			if SERVER == '' or USERNAME == '' or PASSWORD == '': continue #TODO: put error in status
			
			try: 
			
				if PROTOCOL == 0: imap = imaplib.IMAP4_SSL(SERVER) 
				if PROTOCOL == 1: imap = imaplib.IMAP4(SERVER)
				
			except: #TODO: imaplib.IMAP4.error, socket.exception?
				r = 'cannot connect'
				print NAME + ' error -> ' + str(r)
				status.append((NAME,r))
				continue
				
			try: imap.login(USERNAME,PASSWORD)
			except imaplib.IMAP4.error as r:
				print NAME + ' error -> ' + str(r)
				status.append((NAME,r))
				continue
			imap.select()
			i = len(imap.search(None, 'UNSEEN')[1][0].split())

			imap.logout()
	
		elif PROTOCOL == 2:
			
			try:
				AUTH = data[item].get('server','')
			except: continue #TODO: put error in status
			
			if AUTH == '': continue #TODO: put error in status
			
			foo = net.HTTPCookieProcessor()
			opener = net.build_opener(foo)
			opener.addheaders = [('User-agent', 'notewhal/%s' % version)]
			net.install_opener(opener)
			try: 
				req = opener.open(AUTH)
			except net.URLError as r:
				print NAME + ' error -> ' + str(r)
				status.append((NAME,r))
				continue

			d = req.read()
			req.close()
			try:
				d = json.loads(d)['data']['children']
			except:
				d = []
				r = 'cannot load messages'
				print NAME + ' error -> ' + r
				status.append((NAME,r))
			i = len(d)
			
		else:
			print NAME + ' error -> bad protocol'
			status.append((NAME,'bad protocol'))
			continue
			
		checked_profiles[item] = i

	global frame
	if frame:
		event = NewMessagesEvent(checked_profiles=checked_profiles,status=status)
		wx.PostEvent(frame, event)

class Window(wx.Frame):
	def __init__(self):
		wx.Frame.__init__(self, None,-1,'notewhal - Dashboard')
		
		self.Bind(EVT_NEW_MESSAGES, self.OnNewMessages)
		
		self.status = []
		self.last = 0
		self.profiles = {}
		self.data = {}
		self.dummy = {}
		self.cfile = os.path.join(wx.StandardPaths.Get().GetUserConfigDir(),'notewhal.ini')
		self.protocols = ['IMAP4 (SSL)','IMAP4','Reddit']
		
		self.ticker = wx.Timer(self)
		self.Bind(wx.EVT_TIMER,self.OnTick)
		
		self.tray_icon = wx.TaskBarIcon()
		self.tray_icon.Bind(wx.EVT_TASKBAR_LEFT_DCLICK,self.OnItem)
		
		self.menu = wx.Menu()
		
		self.UpdateSettings()
		
		self.tray_icon.Bind(wx.EVT_TASKBAR_RIGHT_UP,self.OnIconRight)
		
		#create and bind property gid context menu
		self.context_menu = wx.Menu()
		self.context_menu.Append(4000,'&Add profile')
		self.context_menu.Append(5000,'&Remove profile')
		self.context_menu.AppendSeparator()
		self.context_menu.Append(6000,'&Up priority')
		self.context_menu.Append(7000,'&Down priority')
		
		self.context_menu.Bind(wx.EVT_MENU,self.OnAdd,id=4000)
		self.context_menu.Bind(wx.EVT_MENU,self.OnRemove,id=5000)
		self.context_menu.Bind(wx.EVT_MENU,self.OnProfileUp,id=6000)
		self.context_menu.Bind(wx.EVT_MENU,self.OnProfileDown,id=7000)
		
		#toolbar
		toolbar = self.CreateToolBar(wx.TB_FLAT|wx.TB_NODIVIDER)
		toolbar.AddLabelTool(200, 'Add profile',wx.Bitmap(os.path.join(os.getcwd(),'data','add.png')))
		toolbar.SetToolShortHelp(200,'Add profile')
		self.Bind(wx.EVT_TOOL,self.OnAdd,id=200)
		toolbar.AddLabelTool(300, 'Remove profile',wx.Bitmap(os.path.join(os.getcwd(),'data','remove.png')))
		toolbar.SetToolShortHelp(300,'Remove profile')
		self.Bind(wx.EVT_TOOL,self.OnRemove,id=300)
		toolbar.AddSeparator()
		toolbar.AddLabelTool(400, 'Up priority',wx.Bitmap(os.path.join(os.getcwd(),'data','up.png')))
		toolbar.SetToolShortHelp(400,'Up priority')
		self.Bind(wx.EVT_TOOL,self.OnProfileUp,id=400)
		toolbar.AddLabelTool(500, 'Down priority',wx.Bitmap(os.path.join(os.getcwd(),'data','down.png')))
		toolbar.SetToolShortHelp(500,'Down priority')
		self.Bind(wx.EVT_TOOL,self.OnProfileDown,id=500)
		toolbar.AddSeparator()
		toolbar.AddLabelTool(600,'Website',wx.Bitmap(os.path.join(os.getcwd(),'data','website.png')))
		toolbar.SetToolShortHelp(600,'Website')
		self.Bind(wx.EVT_TOOL,self.OnWebsite,id=600)
		toolbar.AddSeparator()
		toolbar.AddLabelTool(700,'About',wx.Bitmap(os.path.join(os.getcwd(),'data','about.png')))
		toolbar.SetToolShortHelp(700,'About')
		self.Bind(wx.EVT_TOOL,self.OnAbout,id=700)
		toolbar.AddLabelTool(800,'Contents',wx.Bitmap(os.path.join(os.getcwd(),'data','help.png')))
		toolbar.SetToolShortHelp(800,'Contents')
		self.Bind(wx.EVT_TOOL,self.OnContents,id=800)
		
		toolbar.Realize()
		
		menubar = wx.MenuBar()
		file = wx.Menu()
		file.Append(10,'&Apply\tCtrl+S')
		self.Bind(wx.EVT_MENU,self.OnApply,id=10)
		file.AppendSeparator()
		file.Append(20,'&Close\tAlt+F4')
		self.Bind(wx.EVT_MENU,self.OnFrameClose,id=20)
		file.AppendSeparator()
		file.Append(21,'E&xit')
		self.Bind(wx.EVT_MENU,self.OnExit,id=21)
		
		help = wx.Menu()
		help.Append(30,'&Contents\tF1')
		self.Bind(wx.EVT_MENU,self.OnContents,id=30)
		help.AppendSeparator()
		help.Append(40,'&Website')
		self.Bind(wx.EVT_MENU,self.OnWebsite,id=40)
		help.AppendSeparator()
		help.Append(50,'&About')
		self.Bind(wx.EVT_MENU,self.OnAbout,id=50)
		
		edit = wx.Menu()
		edit.Append(60,'&Add profile')
		edit.Append(70,'&Remove profile')
		self.Bind(wx.EVT_MENU,self.OnAdd,id=60)
		self.Bind(wx.EVT_MENU,self.OnRemove,id=70)
		edit.AppendSeparator()
		edit.Append(80,'&Up priority')
		edit.Append(90,'&Down priority')
		self.Bind(wx.EVT_MENU,self.OnProfileUp,id=80)
		self.Bind(wx.EVT_MENU,self.OnProfileDown,id=90)
		
		menubar.Append(file,'&File')
		menubar.Append(edit,'&Edit')
		menubar.Append(help,'&Help')
		
		self.SetMenuBar(menubar)
		
		#create property grid
		self.pg = wxpg.PropertyGridManager(self,-1,style=wxpg.PG_DESCRIPTION|wxpg.PG_SPLITTER_AUTO_CENTER)
		self.pg.AddPage('Dashboard')
		self.pg.GetGrid().SetMarginColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
		self.pg.GetGrid().SetCaptionBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
		#For wxPython 2.8:
		#self.pg.GetGrid().SetCaptionForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT))
		#For wxPython 2.9:
		self.pg.GetGrid().SetCaptionTextColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT))
		
		
		#bind property grid
		self.pg.Bind(wxpg.EVT_PG_CHANGING,self.OnPGChange)
		self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK,self.OnPGRightClick)
		
		
		#create and bind buttons, bind some frame events, set up sizers for property grid and buttons
		panel = wx.Panel(self,-1)
		apply_btn = wx.Button(panel,-1,'Apply')
		apply_btn.Bind(wx.EVT_BUTTON,self.OnApply)
		about_btn = wx.Button(panel,-1,'About')
		close_btn = wx.Button(panel,-1,'Close')
		close_btn.Bind(wx.EVT_BUTTON, self.OnFrameClose)
		about_btn.Bind(wx.EVT_BUTTON, self.OnAbout)
		
		global version
		version_label = wx.StaticText(panel,-1,'version '+ version)
		version_label.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))

		sizer = wx.BoxSizer(wx.VERTICAL)
		sizer.Add(self.pg,6,wx.EXPAND)
		sizer1 = wx.BoxSizer(wx.HORIZONTAL)
		sizer1.Add(about_btn,0,wx.ALL,4)
		sizer1.Add(version_label,0,wx.ALL|wx.ALIGN_CENTER,4)
		sizer1.AddStretchSpacer()
		sizer1.Add(close_btn,0,wx.TOP|wx.BOTTOM,4)
		sizer1.Add(apply_btn,0,wx.ALL,4)
		panel.SetSizer(sizer1)
		sizer.Add(panel,0,wx.EXPAND)
		self.SetSizer(sizer)
		
		self.Bind(wx.EVT_CLOSE,self.OnFrameClose)
		
		#set icon
		ib = wx.IconBundle()
		ib.AddIconFromFile(os.path.join(os.getcwd(),'data','icon.ico'), wx.BITMAP_TYPE_ICO)
		self.SetIcons(ib)
		
		show_b = not os.path.exists(self.cfile)
		
		config = ConfigParser.ConfigParser()
		config.read(self.cfile)
		if not config.has_section('General'):
			config.add_section('General')
			config.set('General','frequency',5)
			config.set('General','icon',os.path.join(os.getcwd(),'data','email.png'))
			config.set('General','sound',os.path.join(os.getcwd(),'data','notify.wav'))
		
		if config.has_option('General','size') and config.has_option('General','pos'):
			size = config.get('General','size')
			pos = config.get('General','pos')
			
			size = tuple((int(x.strip()) for x in str(size)[1:-1].split(',')))
			pos = tuple((int(x.strip()) for x in str(pos)[1:-1].split(',')))
			
			self.SetSize(size)
			self.SetPosition(pos)
			
		else: self.SetSize((640,480)); self.Center(wx.BOTH)
		
		config.write(open(self.cfile,'w'))
		
		self.dummy = copy.deepcopy(self.data)
		self.fillpropgrid('General',True)
		
		self.fshow = show_b
		self.Show(self.fshow)
		
		self.CheckUpdate()
		
	def OnContents(self, event): os.startfile(os.path.join(os.getcwd(),'notewhal.chm'))
	
	def OnAbout(self, event): 
		global version, gpl
		wx.MessageBox('notewhale ' + version + '\n\nhttp://notewhal.googlecode.com\n' + gpl,'notewhal')
		
	def OnWebsite(self, event): webbrowser.open('http://notewhal.googlecode.com')

	def OnPGRightClick(self, event):
		cat = event.GetPropertyName().rsplit('_',1)[0]
		self.pg.PopupMenu(self.context_menu,self.pg.ScreenToClient(wx.GetMousePosition()))
		
	def loaddata(self):
		self.data.clear()
		config = ConfigParser.ConfigParser()
		
		config.read(self.cfile)
		
		if not config.has_section('General'): config.add_section('General')
		
		for section in config.sections():
			a = {}
			for item in config.items(section):
				a[item[0]] = item[1]
			self.data[section] = a
	
	def OnAdd(self, event):

		c = self.dummy.keys(); c = self.sortthem(c)
		e = 'profile'
		
		if c: e += str(len(c))
		else: e += '0'

		self.dummy[e] = {}
		
		self.dummy[e]['name'] = 'new profile'
		self.dummy[e]['server'] = ''
		self.dummy[e]['loginserv'] = ''
		self.dummy[e]['protocol'] = 0
		self.dummy[e]['program'] = ''
		self.dummy[e]['sound'] = ''
		self.dummy[e]['username'] = ''
		self.dummy[e]['password'] = ''
		self.dummy[e]['color'] = '(0,0,255)'
		
		self.fillpropgrid(e,False)
	
	def OnRemove(self, event):
		
		cat = self.pg.GetPropertyName(self.pg.GetGrid().GetSelectedProperty()).rsplit('_',1)[0]
		if cat == 'General' or cat == '': return
		
		del self.dummy[cat]
		data = self.sortthem(self.dummy.keys())
		
		d = {}
		
		for item in reversed(data):
			num = int(item.rsplit('e')[1])
			if num > int(cat.rsplit('e')[1]):
				d.setdefault('profile'+str(num-1),self.dummy[item])
				del self.dummy[item]
				
		self.dummy.update(d)
		
		bar = int(cat.rsplit('e')[1])
		if bar >= len(data): bar -= 1
		foo = 'profile'+str(bar)
		if bar < 0: foo = 'General'
		
		self.fillpropgrid(foo, False)
		
	def OnProfileUp(self, event):
		
		cat = self.pg.GetPropertyName(self.pg.GetGrid().GetSelectedProperty()).rsplit('_',1)[0]
		if cat == 'General' or cat == '': return
		
		num = int(cat.rsplit('e')[1])
		
		if num <= 0: return #return if current profile is the maximum priority
		
		data = copy.deepcopy(self.dummy['profile'+str(num)])
		
		self.dummy['profile'+str(num)].update(self.dummy['profile'+str(num-1)])
		self.dummy['profile'+str(num-1)].update(data)
		
		self.fillpropgrid('profile'+str(num-1),False)
		
	def OnProfileDown(self, event):
		
		cat = self.pg.GetPropertyName(self.pg.GetGrid().GetSelectedProperty()).rsplit('_',1)[0]
		if cat == 'General' or cat == '': return
		
		l = len(self.dummy.keys())-2
		num = int(cat.rsplit('e')[1])
		
		if num >= l: return #return if current profile is the minimum priority
		
		data = copy.deepcopy(self.dummy['profile'+str(num)])
		
		self.dummy['profile'+str(num)].update(self.dummy['profile'+str(num+1)])
		self.dummy['profile'+str(num+1)].update(data)
		
		self.fillpropgrid('profile'+str(num+1),False)
	
	def OnPGChange(self,event):
		val = event.GetValue()
		cat = event.GetPropertyName().rsplit('_',1)[0]
		name = event.GetPropertyName().rsplit('_',1)[1]
		
		b = []

		c = self.dummy.keys(); c = self.sortthem(c);
		for x in c:
			b.append(self.dummy[x]['name'])
		b.append('General')

		if name == 'name':
			if val in b:
				self.pg.Freeze()
				self.pg.RefreshGrid()
				self.pg.Thaw()
				event.Veto(); return
			else:
				self.pg.SetPropertyLabel(cat,val)
		if name == 'protocol':
			if val == 2:
				#self.pg.HideProperty(cat + '_password',True)
				self.pg.SetPropertyValue(cat + '_loginserv','http://reddit.com')
				self.dummy[cat]['loginserv'] = 'http://reddit.com'
				self.pg.SetPropertyLabel(cat + '_server','JSON')
				self.pg.SetPropertyHelpString(cat + '_server','The JSON auth URL for unread messages and located in your reddit.com preferences.')
			else:
				self.pg.SetPropertyHelpString(cat + '_server','The URL to the server for this profile.')
				self.pg.SetPropertyLabel(cat + '_server','Server')
				#self.pg.HideProperty(cat + '_password',False)

		self.dummy[cat][name] = str(val)
		
		self.pg.Freeze()
		self.pg.RefreshGrid()
		self.pg.Thaw()
	

	def buildmenu(self):

		for b in self.menu.GetMenuItems(): self.menu.DestroyItem(b)
	
		c = self.getkeys()
		
		for a, item in enumerate(c):
			self.menu.Append(a, self.data[item]['name'] + '\t0')
			self.menu.Bind(wx.EVT_MENU,self.OnItem,id=a)

		self.menu.AppendSeparator()
		self.menu.Append(2000,'&Check now')
		self.menu.AppendSeparator()
		self.menu.Append(3000,'&Dashboard')
		self.menu.Append(4000,'&Status')
		self.menu.AppendSeparator()
		self.menu.Append(1001,'E&xit')
		
		self.menu.Bind(wx.EVT_MENU,self.OnExit, id=1001)
		self.menu.Bind(wx.EVT_MENU,self.OnCheckNow, id=2000)
		self.menu.Bind(wx.EVT_MENU,self.OnDashboard, id=3000)
		self.menu.Bind(wx.EVT_MENU,self.OnStatus, id=4000)
	
	def getkeys(self):
		foo = self.data.keys()
		foo = self.sortthem(foo)
		return foo
		
	def UpdateSettings(self):
		
		self.loaddata()

		freq = self.data['General'].setdefault('frequency',5)
		icon = self.data['General'].setdefault('icon',os.path.join(os.getcwd(),'data','email.png'))
		if not os.path.exists(icon): icon = os.path.join(os.getcwd(),'data','email.png')
		self.gsound = self.data['General'].setdefault('sound',os.path.join(os.getcwd(),'data','notify.wav'))
		if not os.path.exists(self.gsound) and self.gsound != '#' and self.gsound != '': self.gsound = os.path.join(os.getcwd(),'data','notify.wav')
		
		if self.ticker.IsRunning(): self.ticker.Stop()
	
		self.source = Image.open(icon, 'r')
		self.d = self.source.copy()

		image = apply(wx.EmptyImage, self.source.size)
		image.SetData(self.source.convert('RGB').tostring())
		image.SetAlphaData(self.source.convert('RGBA').tostring()[3::4])
		image = image.ConvertToBitmap()

		c = self.getkeys()
		self.profiles.clear()
		for item in c: self.profiles[item] = 0
		
		icon = wx.EmptyIcon()
		icon.CopyFromBitmap(image)
		self.tray_icon.SetIcon(icon,self.maketooltip())
		
		if float(freq) < 0.5: freq = 0.5
		else: freq = float(freq)
		self.ticker.Start(freq*60000)
		self.buildmenu()
		wx.CallAfter(self.DoCheckNow)
	
	def sortthem(self,tosort):
		b = []
		if('General' in tosort): tosort.remove('General')
		for x in tosort: b.append(int(filter(type(x).isdigit, x)))
		b.sort()
		return ['profile'+str(x) for x in b]
		
	def OnTick(self,event): self.DoCheckNow()
		
	def dosave(self):
	
		self.data = copy.deepcopy(self.dummy)
	
		config = ConfigParser.ConfigParser()
		
		for section in self.data.keys():
			config.add_section(section)
			for item in self.data[section].items():
				config.set(section,item[0],item[1])

		config.write(open(self.cfile, 'w'))

	def OnApply(self, event):
		
		self.dosave()
		self.UpdateSettings()
		self.Close()

	def fillpropgrid(self,sel,revert):
		
		if revert: self.loaddata()
		
		c = self.dummy.keys(); c = self.sortthem(c); c.insert(0,'General')
		
		options = {}
		
		self.pg.Freeze()
		self.pg.GetGrid().Clear()

		self.pg.Append(wxpg.PropertyCategory('General'))
		
		for item in c:
			
			if item != 'General':
				self.pg.Append(wxpg.PropertyCategory(self.dummy[item].setdefault('name','new profile'),item))
			
			for x in self.dummy[item]: options[x] = self.dummy[item][x]
			
			if item == 'General':
				
				self.pg.Append(wxpg.FloatProperty('frequency',item + '_' + 'frequency',value=float(options.setdefault('frequency',5))))
				self.pg.SetPropertyHelpString(item + '_frequency','The interval between checks in minutes. Minimum is 0.5 (30 seconds).')
				self.pg.SetPropertyAttribute(item + '_frequency','Min',0.5)
				
				val = options.setdefault('sound',os.path.join(os.getcwd(),'data','notify.wav'))
				if not os.path.exists(val) and val != '#' and val != '': val = os.path.join(os.getcwd(),'data','notify.wav')
				self.pg.Append(wxpg.FileProperty('sound',item + '_' + 'sound',value=val))
				self.pg.SetPropertyHelpString(item + '_sound','The sound that plays when messages are detected. This sound plays for any profiles with its own sound property left blank. To disable sound globally, enter "#".')
				
				val = options.setdefault('icon',os.path.join(os.getcwd(),'data','email.png'))
				if not os.path.exists(val): val = os.path.join(os.getcwd(),'data','email.png')
				self.pg.Append(wxpg.FileProperty('icon',item + '_' + 'icon',value=val))
				self.pg.SetPropertyHelpString(item + '_icon','The icon that sits in the tray and changes colors to notify you of new messages.')
				
			else:
			
				self.pg.Append(wxpg.StringProperty('Name',item + '_' + 'name',value=options.setdefault('name','new profile')))
				self.pg.SetPropertyHelpString(item + '_name','The name of this profile.')
				
				self.pg.Append(wxpg.EnumProperty('Protocol',item + '_protocol',self.protocols,[0,1,2],int(options.setdefault('protocol',0))))
				self.pg.SetPropertyHelpString(item + '_protocol','The protocol for this profile.')
				
				self.pg.Append(wxpg.StringProperty('User Name',item + '_username',value=options.setdefault('username','')))
				self.pg.SetPropertyHelpString(item + '_username','The name you use to log into your account for this profile - sometimes the domain is required (e.g., username@server.com).')
				
				self.pg.Append(wxpg.StringProperty('Password',item + '_password',value=options.setdefault('password','')))
				self.pg.SetPropertyAttribute(item + '_' + 'password', 'Password', 1)
				self.pg.SetPropertyHelpString(item + '_password','The password for this profile. The password is not required for the Reddit protocol but can be used for HTTP Login or Program.')
				
				if int(options.setdefault('protocol',0)) == 2:
					self.pg.Append(wxpg.StringProperty('JSON',item + '_server',value=options.setdefault('server','')))
					self.pg.SetPropertyHelpString(item + '_server','The JSON auth URL for unread messages and located in your reddit.com preferences.')
				else:
					self.pg.Append(wxpg.StringProperty('Server',item + '_server',value=options.setdefault('server','')))
					self.pg.SetPropertyHelpString(item + '_server','The URL to the server for this profile.')
				
				self.pg.Append(wxpg.LongStringProperty('HTTP Login',item + '_loginserv',value=options.setdefault('loginserv','')))
				self.pg.SetPropertyHelpString(item + '_loginserv','The URL where you log in to check your messages via HTTP. You may leave this blank if you do not require the functionality.\n\n$username is a template for your username and $password is a template for your password.')
				
				if int(options.setdefault('protocol',0)) == 2:
					if options.get('loginserv','') == '': self.pg.SetPropertyValue(item + '_loginserv','http://reddit.com')
				
				self.pg.Append(wxpg.FileProperty('Program',item + '_program',value=options.setdefault('program','')))
				self.pg.SetPropertyHelpString(item + '_program','The program to launch when you select the profile. You may leave this blank if you do not require the functionality.\n\n$username is a template for your username and $password is a template for your password. The templates may be used as program arguments.')
				
				self.pg.Append(wxpg.FileProperty('Sound',item + '_sound',value=options.setdefault('sound','')))
				self.pg.SetPropertyHelpString(item + '_sound','The sound that plays for this profile when new messages are detected. To disable sound for this profile when a global sound is set, enter "#".')
				
				rgb = tuple((int(x.strip()) for x in str(options.setdefault('color','(0,0,255)'))[1:-1].split(',')))
				self.pg.Append(wxpg.ColourProperty('Color',item + '_color',value=rgb))
				self.pg.SetPropertyHelpString(item + '_color','The color that the icon for this profile changes to when new new messages are detected.')
				
				self.pg.SetPropertyHelpString(item,'Priority: ' + filter(type(item).isdigit, item))
				
		self.pg.SetPropertyHelpString('General','These settings apply to all profiles.\n\nRight-click a cell for options to add/delete profiles.')

		#TODO: (maybe) if profile is already collapsed don't expand it
		if sel:
			self.pg.CollapseAll()
			self.pg.Expand('General')
			if sel != 'General': self.pg.Expand(sel)
			self.pg.SelectProperty(sel)
			self.pg.GetGrid().Scroll(-1,self.pg.GetSelectedProperty().GetY()/self.pg.GetGrid().GetRowHeight())
		
		self.pg.RefreshGrid()
		self.pg.Thaw()
	
	def OnDashboard(self,event):
	
		self.dummy = copy.deepcopy(self.data)

		self.fillpropgrid('General',True)

		self.fshow^=1

		if not self.fshow: self.fillpropgrid(0,True) #safety function, revert the prop grid, select nothing, on settings close, TODO: might not be necessary
		self.Show(self.fshow)

	def OnFrameClose(self,event):
	
		self.fillpropgrid(0,True)
		self.fshow = 0
		self.Show(self.fshow)
	
	def OnItem(self,event):
	
		id = event.GetId()
		c = self.getkeys()
		if id == -1: id = self.last
		loginserv = self.data[c[id]]['loginserv']
		program = self.data[c[id]]['program']
		user = self.data[c[id]]['username'].split('@',1)[0]
		passw = self.data[c[id]]['password']
														
		if loginserv:
		
			url = ''
			http = loginserv.find('http://')
			https = loginserv.find('https://')
			loginserv = Template(loginserv).safe_substitute(username=user,password=passw)
			if http == -1 and https == -1: url = 'http://' + loginserv
			elif https != -1: url = loginserv
			elif http != -1: url = loginserv
			webbrowser.open(url)
		if program:
			program = Template(program).safe_substitute(username=user,password=passw)
			subprocess.Popen(program)
			
		self.last = id #TODO: needs testing

	def DoCheckNow(self): wthread(CheckMessages,self.getkeys(),self.data)

	def OnCheckNow(self,event): self.DoCheckNow()
	
	def maketooltip(self):
		a = ''
		c = self.profiles.keys(); c = self.sortthem(c)

		for x in c:
			if self.profiles[x] != 0:
				a += self.data[x]['name'] +': '+str(self.profiles[x])+'\n'
				
		if not a: a = 'No new messages'
		
		return a
		
	def OnIconRight(self, event): self.tray_icon.PopupMenu(self.menu)
		
	def OnNewMessages(self, event):
		global frame
		if not frame: return
	
		c = self.getkeys()
		checked_profiles = event.checked_profiles
		self.status = event.status
		foo = []

		for a, item in enumerate(c):

			i = checked_profiles.get(item,0)
			l = self.profiles[item]
			
			try:
				SOUND = self.data[item].setdefault('sound','')
				NAME = self.data[item].setdefault('name','')
			except: continue

			if l != i and l < i and self.gsound != '#':
				if SOUND != '' and SOUND != '#':
					snd = wx.Sound(SOUND)
					if snd.IsOk(): snd.Play(wx.SOUND_ASYNC)
				else:
					snd = wx.Sound(self.gsound)
					if snd.IsOk(): snd.Play(wx.SOUND_ASYNC)
			
			if l != i:
				self.profiles[item] = i
				self.menu.FindItemByPosition(a).SetText(NAME + '\t' + str(i))
			
			if i:

				self.last = int(item.rsplit('e')[1])
				foo.append(item)
				
		d = self.source.copy()
		
		for i, x in enumerate(foo):
		
			COLOR = self.data[x]['color']
			rgb = tuple((int(x.strip()) for x in str(COLOR)[1:-1].split(',')))
			w,h = self.source.size
			b = Image.new('RGBA',(w,h),rgb)
			
			c = (0,0)

			if i == 0: d = ImageChops.lighter(self.source,b)
			elif i == 1:
				t = (0,0,w,h/2)
				e = ImageChops.lighter(self.source,b).crop((0,0,w,h/2))
				d.paste(e,(0,0))
			elif i == 2:
				t = (0,0,w/2,h/2)
				e = ImageChops.lighter(self.source,b).crop(t)
				d.paste(e,c)
			elif i == 3:
				t = (w/2,h/2,w,h)
				c = (w/2,h/2)
				e = ImageChops.lighter(self.source,b).crop(t)
				d.paste(e,c)

		if self.status:
			n = Image.open(os.path.join(os.getcwd(),'data','error.png'),'r')
			d.paste(n,(0,(self.source.size[1]-n.size[1])-2))
				
		image = apply(wx.EmptyImage, d.size)
		image.SetData(d.convert('RGB').tostring())
		image.SetAlphaData(self.source.convert('RGBA').tostring()[3::4])
		image = image.ConvertToBitmap()
		
		icon = wx.EmptyIcon()
		icon.CopyFromBitmap(image)
		self.tray_icon.SetIcon(icon,self.maketooltip())

	def OnExit(self, event): wx.CallAfter(self.DoExit)
		
	def DoExit(self):
	
		config = ConfigParser.ConfigParser()
		config.read(self.cfile)
		if not config.has_section('General'): config.add_section('General')
		config.set('General', 'size', self.GetSizeTuple())
		config.set('General', 'pos', self.GetPositionTuple())
		config.write(open(self.cfile, 'w'))
		
		if self.ticker.IsRunning(): self.ticker.Stop()
		
		self.Bind(EVT_NEW_MESSAGES, None)
		
		self.tray_icon.Destroy()
		self.Destroy()
		
	def OnStatus(self, event):
	
		if not self.status: 
			wx.MessageBox('All profiles are OK!', 'notewhal - Status')
			return
			
			
		status_frm = wx.Dialog(self,-1,'notewhal - Status',wx.DefaultPosition,wx.Size(300,190),wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
		
		sizer = wx.BoxSizer(wx.VERTICAL)
		list = wx.ListView(status_frm)

		list.InsertColumn(0,'Profile')
		list.InsertColumn(1,'Error',wx.LIST_FORMAT_LEFT,175)

		for a, i in enumerate(self.status):
			b = list.InsertStringItem(a,unicode(i[0]))
			list.SetStringItem(b,1,unicode(i[1]))
		
		sizer1 = wx.BoxSizer(wx.HORIZONTAL)
		sizer1.Add(list,1,wx.ALL|wx.EXPAND,4)
		sizer.Add(sizer1,wx.EXPAND)
		status_frm.SetSizer(sizer1)
		status_frm.ShowModal()
		
	def CheckUpdate(self):
		
		try: req = net.urlopen('http://notewhal.googlecode.com/hg/version.py')
		except net.URLError as r: 
			print 'notewhal error -> ' + str(r)
			return
			
		d = req.read()
		req.close()
		
		d = d.split('=')[1]
		d = d.strip(); d = d.strip("'")
		
		c_v = d
		global version
		t_v = version
		
		c_v = [int(x) for x in c_v.split('.')]
		t_v = [int(x) for x in t_v.split('.')]
		
		if len(c_v) > len(t_v):
			t_v = t_v + [0]*(len(c_v) - len(t_v))
		if len(t_v) > len(c_v):
			c_v = c_v + [0]*(len(t_v) - len(c_v))
				
		for i, x in enumerate(t_v):
			if x < c_v[i]:
				a = wx.MessageBox('A new version of notewhal is available.\n\nWould you like to update?','notewhal',wx.ICON_QUESTION|wx.YES_NO)
				if a == wx.YES: webbrowser.open('http://code.google.com/p/notewhal/downloads/list')
				break

app = wx.PySimpleApp()
frame = Window()
app.MainLoop()
